#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <opus/opus.h>
#include <ogg/ogg.h>
#include <time.h>

// 核心配置参数
#define FRAME_DURATION_MS 20
#define MAX_OPUS_PACKET_SIZE 4000
#define OPUS_BITRATE 64000
#define OPUS_MAX_FRAME_SIZE 5760

// 前向声明
int write_opus_id_header(ogg_stream_state *os, int channels, int sample_rate, FILE *opus_out);
int write_opus_comment_header(ogg_stream_state *os, const char *encoder_info, FILE *opus_out);

/**
 * 解析WAV头部
 */
int parse_wav_header(FILE *wav_file, uint32_t *sample_rate, uint16_t *channels, uint16_t *bits_per_sample) {
    uint8_t riff_id[4];
    uint32_t riff_size;
    uint8_t wave_id[4];

    if (fread(riff_id, 1, 4, wav_file) != 4 || memcmp(riff_id, "RIFF", 4) != 0) {
        fprintf(stderr, "错误:不是RIFF格式文件\n");
        return -1;
    }
    if (fread(&riff_size, 4, 1, wav_file) != 1) { fprintf(stderr, "错误:读取RIFF大小失败\n"); return -1; }
    if (fread(wave_id, 1, 4, wav_file) != 4 || memcmp(wave_id, "WAVE", 4) != 0) {
        fprintf(stderr, "错误:不是WAVE格式文件\n");
        return -1;
    }

    uint8_t subchunk_id[4];
    uint32_t subchunk_size;
    uint16_t audio_format;

    while (1) {
        if (fread(subchunk_id, 1, 4, wav_file) != 4) { fprintf(stderr, "错误:找不到fmt子块\n"); return -1; }
        if (fread(&subchunk_size, 4, 1, wav_file) != 1) { fprintf(stderr, "错误:读取子块大小失败\n"); return -1; }
        if (memcmp(subchunk_id, "fmt ", 4) == 0) break;
        if (fseek(wav_file, subchunk_size, SEEK_CUR) != 0) { fprintf(stderr, "错误:跳过子块失败\n"); return -1; }
    }

    if (fread(&audio_format, 2, 1, wav_file) != 1) { fprintf(stderr, "错误:读取音频格式失败\n"); return -1; }
    if (fread(channels, 2, 1, wav_file) != 1) { fprintf(stderr, "错误:读取声道数失败\n"); return -1; }
    if (fread(sample_rate, 4, 1, wav_file) != 1) { fprintf(stderr, "错误:读取采样率失败\n"); return -1; }
    
    uint32_t byte_rate;
    uint16_t block_align;
    if (fread(&byte_rate, 4, 1, wav_file) != 1) { fprintf(stderr, "错误:读取字节率失败\n"); return -1; }
    if (fread(&block_align, 2, 1, wav_file) != 1) { fprintf(stderr, "错误:读取块对齐失败\n"); return -1; }
    if (fread(bits_per_sample, 2, 1, wav_file) != 1) { fprintf(stderr, "错误:读取位深失败\n"); return -1; }

    if (subchunk_size > 16) {
        if (fseek(wav_file, subchunk_size - 16, SEEK_CUR) != 0) { fprintf(stderr, "错误:跳过扩展字段失败\n"); return -1; }
    }

    while (1) {
        if (fread(subchunk_id, 1, 4, wav_file) != 4) { fprintf(stderr, "错误:找不到data子块\n"); return -1; }
        if (fread(&subchunk_size, 4, 1, wav_file) != 1) { fprintf(stderr, "错误:读取data大小失败\n"); return -1; }
        if (memcmp(subchunk_id, "data", 4) == 0) break;
        if (fseek(wav_file, subchunk_size, SEEK_CUR) != 0) { fprintf(stderr, "错误:跳过子块失败\n"); return -1; }
    }

    if (audio_format != 1) { fprintf(stderr, "错误:仅支持PCM编码\n"); return -1; }
    if (*channels != 1 && *channels != 2) { fprintf(stderr, "错误:仅支持1/2声道\n"); return -1; }
    if (*sample_rate != 8000 && *sample_rate != 16000 && *sample_rate != 48000) {
        fprintf(stderr, "错误:仅支持8K/16K/48K采样率\n");
        return -1;
    }
    if (*bits_per_sample != 16 && *bits_per_sample != 24 && *bits_per_sample != 32) {
        fprintf(stderr, "错误:仅支持16/24/32位深\n");
        return -1;
    }

    return 0;
}

/**
 * 高位深PCM转16位
 */
void pcm_to_16bit(const void *src, int16_t *dst, uint32_t sample_count, uint16_t bits_per_sample) {
    const int32_t *src_32 = (const int32_t *)src;
    for (uint32_t i = 0; i < sample_count; i++) {
        switch (bits_per_sample) {
            case 24: dst[i] = (int16_t)(src_32[i] >> 8); break;
            case 32: dst[i] = (int16_t)(src_32[i] >> 16); break;
            case 16: dst[i] = ((const int16_t *)src)[i]; break;
            default: dst[i] = 0; break;
        }
    }
}

/**
 * 写入OPUS ID头
 */
int write_opus_id_header(ogg_stream_state *os, int channels, int sample_rate, FILE *opus_out) {
    uint8_t id_header[19] = {
        'O', 'p', 'u', 's', 'H', 'e', 'a', 'd',
        0x01, (uint8_t)channels, 0x00, 0x00,
        (uint8_t)(sample_rate >> 24), (uint8_t)(sample_rate >> 16),
        (uint8_t)(sample_rate >> 8), (uint8_t)sample_rate,
        0x00, 0x00
    };

    ogg_packet op = {0};
    op.packet = id_header;
    op.bytes = sizeof(id_header);
    op.b_o_s = 1;
    op.packetno = 0;

    if (ogg_stream_packetin(os, &op) != 0) {
        fprintf(stderr, "错误:ID头加入流失败\n");
        return -1;
    }

    ogg_page og;
    while (ogg_stream_pageout(os, &og) != 0) {
        fwrite(og.header, 1, og.header_len, opus_out);
        fwrite(og.body, 1, og.body_len, opus_out);
    }
    return 0;
}

/**
 * 写入OPUS Comment头
 */
int write_opus_comment_header(ogg_stream_state *os, const char *encoder_info, FILE *opus_out) {
    int info_len = strlen(encoder_info);
    int header_len = 8 + 4 + 4 + info_len + 4;
    uint8_t *comment_header = (uint8_t*)malloc(header_len);
    if (!comment_header) return -1;

    int pos = 0;
    memcpy(comment_header + pos, "OpusTags", 8); pos +=8;
    *(uint32_t*)(comment_header + pos) = 0x00000001; pos +=4;
    *(uint32_t*)(comment_header + pos) = info_len; pos +=4;
    memcpy(comment_header + pos, encoder_info, info_len); pos += info_len;
    *(uint32_t*)(comment_header + pos) = 0;

    ogg_packet op = {0};
    op.packet = comment_header;
    op.bytes = header_len;
    op.packetno = 1;

    if (ogg_stream_packetin(os, &op) != 0) {
        free(comment_header);
        return -1;
    }

    ogg_page og;
    while (ogg_stream_pageout(os, &og) != 0) {
        fwrite(og.header, 1, og.header_len, opus_out);
        fwrite(og.body, 1, og.body_len, opus_out);
    }

    free(comment_header);
    return 0;
}

/**
 * WAV转OPUS主函数
 */
int ConvertWavToOpus(const char *input_file, const char *output_file) 
{

    printf("ConvertWavToOpus() 输入文件: %s, 输出文件: %s\n", input_file, output_file);

    FILE *wav_in = fopen(input_file, "rb");
    FILE *opus_out = fopen(output_file, "wb");
    if (!wav_in || !opus_out) { perror("错误:打开文件失败"); return 1; }

    uint32_t sample_rate;
    uint16_t channels, bits_per_sample;
    if (parse_wav_header(wav_in, &sample_rate, &channels, &bits_per_sample) != 0) {
        fclose(wav_in); fclose(opus_out); return 1;
    }

    int opus_error;
    OpusEncoder *encoder = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_AUDIO, &opus_error);
    if (opus_error != OPUS_OK) {
        fprintf(stderr, "错误:创建编码器失败：%s\n", opus_strerror(opus_error));
        fclose(wav_in); fclose(opus_out); return 1;
    }
    opus_encoder_ctl(encoder, OPUS_SET_BITRATE(OPUS_BITRATE));

    ogg_stream_state os;
    srand(time(NULL));
    if (ogg_stream_init(&os, rand()) != 0) {
        fprintf(stderr, "错误:初始化Ogg流失败\n");
        opus_encoder_destroy(encoder); fclose(wav_in); fclose(opus_out); return 1;
    }

    // 写入OPUS头部（修复：移除OPUS_VERSION_STRING）
    if (write_opus_id_header(&os, channels, sample_rate, opus_out) != 0) {
        fprintf(stderr, "错误:写入ID头失败\n");
        ogg_stream_clear(&os); opus_encoder_destroy(encoder); fclose(wav_in); fclose(opus_out); return 1;
    }
    char encoder_info[128];
    snprintf(encoder_info, sizeof(encoder_info), "Encoder=Custom WavToOpus (Bitrate=%dkbps)", OPUS_BITRATE/1000);
    if (write_opus_comment_header(&os, encoder_info, opus_out) != 0) {
        fprintf(stderr, "错误:写入Comment头失败\n");
        ogg_stream_clear(&os); opus_encoder_destroy(encoder); fclose(wav_in); fclose(opus_out); return 1;
    }

    uint32_t frame_size = (sample_rate * FRAME_DURATION_MS) / 1000;
    uint32_t src_buf_size = frame_size * channels * (bits_per_sample / 8);
    void *src_buffer = malloc(src_buf_size);
    int16_t *pcm_16bit_buf = (int16_t*)malloc(OPUS_MAX_FRAME_SIZE * channels * sizeof(int16_t));
    unsigned char *opus_buffer = (unsigned char*)malloc(MAX_OPUS_PACKET_SIZE);

    #define CLEANUP \
        free(src_buffer); free(pcm_16bit_buf); free(opus_buffer); \
        opus_encoder_destroy(encoder); ogg_stream_clear(&os); \
        fclose(wav_in); fclose(opus_out)

    if (!src_buffer || !pcm_16bit_buf || !opus_buffer) {
        fprintf(stderr, "错误:内存分配失败\n");
        CLEANUP; return 1;
    }

    // 合法帧长定义
    uint32_t valid_frame_sizes[6], default_frame_size;
    switch (sample_rate) {
        case 48000:
            valid_frame_sizes[0]=120; valid_frame_sizes[1]=240; valid_frame_sizes[2]=480;
            valid_frame_sizes[3]=960; valid_frame_sizes[4]=1920; valid_frame_sizes[5]=2880;
            default_frame_size=960; break;
        case 16000:
            valid_frame_sizes[0]=40; valid_frame_sizes[1]=80; valid_frame_sizes[2]=160;
            valid_frame_sizes[3]=320; valid_frame_sizes[4]=640; valid_frame_sizes[5]=960;
            default_frame_size=320; break;
        case 8000:
            valid_frame_sizes[0]=20; valid_frame_sizes[1]=40; valid_frame_sizes[2]=80;
            valid_frame_sizes[3]=160; valid_frame_sizes[4]=320; valid_frame_sizes[5]=480;
            default_frame_size=160; break;
        default: fprintf(stderr, "错误:不支持的采样率\n"); CLEANUP; return 1;
    }

    auto find_nearest_valid = [&](uint32_t current) -> uint32_t {
        if (current == 0) return default_frame_size;
        for (auto s : valid_frame_sizes) if (s >= current) return s;
        return valid_frame_sizes[5];
    };

    // 编码循环
    ogg_page og;
    ogg_packet op;
    uint32_t packet_count = 2;
    uint64_t granulepos = 0;

    while (1) {
        size_t bytes_read = fread(src_buffer, 1, src_buf_size, wav_in);
        if (bytes_read == 0) {
            if (feof(wav_in)) break;
            perror("错误:读取WAV失败"); CLEANUP; return 1;
        }

        uint32_t actual_samples = bytes_read / (bits_per_sample / 8) / channels;
        if (actual_samples == 0) continue;

        uint32_t remaining = actual_samples;
        const char *src_ptr = (const char*)src_buffer;
        while (remaining > 0) {
            uint32_t batch = remaining > OPUS_MAX_FRAME_SIZE ? OPUS_MAX_FRAME_SIZE : remaining;
            uint32_t valid = find_nearest_valid(batch);
            bool pad = (valid != batch);

            int16_t *encode_buf = pcm_16bit_buf;
            int16_t *temp = nullptr;
            if (pad) {
                temp = (int16_t*)malloc(valid * channels * sizeof(int16_t));
                if (!temp) { fprintf(stderr, "错误:分配缓存失败\n"); CLEANUP; return 1; }
                encode_buf = temp;
            }

            pcm_to_16bit(src_ptr, encode_buf, batch * channels, bits_per_sample);
            if (pad) {
                memset(encode_buf + batch * channels, 0, (valid - batch) * channels * sizeof(int16_t));
            }

            int opus_len = opus_encode(encoder, encode_buf, valid, opus_buffer, MAX_OPUS_PACKET_SIZE);
            if (temp) free(temp);
            if (opus_len < 0) {
                fprintf(stderr, "错误:编码失败：%s\n", opus_strerror(opus_len));
                CLEANUP; return 1;
            }

            op.packet = opus_buffer;
            op.bytes = opus_len;
            op.b_o_s = 0;
            op.e_o_s = 0;
            op.granulepos = granulepos + valid;
            op.packetno = packet_count++;

            if (ogg_stream_packetin(&os, &op) != 0) {
                fprintf(stderr, "错误:加入Ogg流失败\n");
                CLEANUP; return 1;
            }

            // 修复：传递&os（指针）给ogg_stream_pageout
            while (ogg_stream_pageout(&os, &og) != 0) {
                fwrite(og.header, 1, og.header_len, opus_out);
                fwrite(og.body, 1, og.body_len, opus_out);
            }

            remaining -= batch;
            src_ptr += batch * channels * (bits_per_sample / 8);
            granulepos += valid;
        }
    }

    // 处理剩余数据
    int final_len = opus_encode(encoder, (const int16_t*)nullptr, 0, opus_buffer, MAX_OPUS_PACKET_SIZE);
    if (final_len > 0) {
        op.packet = opus_buffer;
        op.bytes = final_len;
        op.e_o_s = 1;
        op.granulepos = granulepos;
        op.packetno = packet_count++;
        ogg_stream_packetin(&os, &op);

        // 修复：传递&os（指针）
        while (ogg_stream_pageout(&os, &og) != 0) {
            fwrite(og.header, 1, og.header_len, opus_out);
            fwrite(og.body, 1, og.body_len, opus_out);
        }
    } else if (final_len < 0 && final_len != -1) {
        fprintf(stderr, "错误:处理剩余数据失败：%s\n", opus_strerror(final_len));
        CLEANUP; return 1;
    }

    // 修复：传递&os（指针）给ogg_stream_flush
    while (ogg_stream_flush(&os, &og) != 0) {
        fwrite(og.header, 1, og.header_len, opus_out);
        fwrite(og.body, 1, og.body_len, opus_out);
    }

    CLEANUP;
    printf("转换完成！输出：%s\n", output_file);
    return 0;
}

